package com.example.common.utils;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis 工具类 - 基于 StringRedisTemplate 实现
 *
 * @author qwert
 * @version 1.0.0
 * @create 2025/8/6 22:12
 */
@Component
@RequiredArgsConstructor
public class RedisUtils {

    private final StringRedisTemplate stringRedisTemplate;

    // ============================= 通用方法 =============================

    /**
     * 删除键
     *
     * @param key 键
     * @return 是否删除成功
     */
    public boolean delete(String key) {
        return Boolean.TRUE.equals(stringRedisTemplate.delete(key));
    }

    /**
     * 批量删除键
     *
     * @param keys 键集合
     * @return 删除的数量
     */
    public long delete(Collection<String> keys) {
        Long count = stringRedisTemplate.delete(keys);
        return count != null ? count : 0;
    }

    /**
     * 设置过期时间
     *
     * @param key     键
     * @param timeout 过期时间
     * @param unit    时间单位
     * @return 是否设置成功
     */
    public boolean expire(String key, long timeout, TimeUnit unit) {
        return Boolean.TRUE.equals(stringRedisTemplate.expire(key, timeout, unit));
    }

    /**
     * 获取过期时间
     *
     * @param key  键
     * @param unit 时间单位
     * @return 剩余过期时间
     */
    public long getExpire(String key, TimeUnit unit) {
        Long expire = stringRedisTemplate.getExpire(key, unit);
        return expire != null ? expire : -2;
    }

    /**
     * 判断键是否存在
     *
     * @param key 键
     * @return 是否存在
     */
    public boolean hasKey(String key) {
        return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key));
    }

    // ============================= String 类型操作 =============================

    /**
     * 设置字符串值
     *
     * @param key   键
     * @param value 值
     */
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    /**
     * 设置带过期时间的字符串值
     *
     * @param key     键
     * @param value   值
     * @param timeout 过期时间
     * @param unit    时间单位
     */
    public void set(String key, String value, long timeout, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    /**
     * 当键不存在时设置值
     *
     * @param key   键
     * @param value 值
     * @return 是否设置成功
     */
    public boolean setIfAbsent(String key, String value) {
        return Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(key, value));
    }

    /**
     * 当键不存在时设置带过期时间的值
     *
     * @param key     键
     * @param value   值
     * @param timeout 过期时间
     * @param unit    时间单位
     * @return 是否设置成功
     */
    public boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {
        return Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit));
    }

    /**
     * 获取字符串值
     *
     * @param key 键
     * @return 值
     */
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 获取并设置新值
     *
     * @param key   键
     * @param value 新值
     * @return 旧值
     */
    public String getAndSet(String key, String value) {
        return stringRedisTemplate.opsForValue().getAndSet(key, value);
    }

    /**
     * 自增操作
     *
     * @param key 键
     * @return 自增后的值
     */
    public long increment(String key) {
        return increment(key, 1);
    }

    /**
     * 自增指定数值
     *
     * @param key   键
     * @param delta 增量
     * @return 自增后的值
     */
    public long increment(String key, long delta) {
        Long value = stringRedisTemplate.opsForValue().increment(key, delta);
        return value != null ? value : 0;
    }

    /**
     * 自减操作
     *
     * @param key 键
     * @return 自减后的值
     */
    public long decrement(String key) {
        return decrement(key, 1);
    }

    /**
     * 自减指定数值
     *
     * @param key   键
     * @param delta 减量
     * @return 自减后的值
     */
    public long decrement(String key, long delta) {
        Long value = stringRedisTemplate.opsForValue().decrement(key, delta);
        return value != null ? value : 0;
    }

    // ============================= Hash 类型操作 =============================

    /**
     * 设置哈希字段值
     *
     * @param key     键
     * @param hashKey 哈希键
     * @param value   值
     */
    public void hPut(String key, String hashKey, String value) {
        stringRedisTemplate.opsForHash().put(key, hashKey, value);
    }

    /**
     * 批量设置哈希字段
     *
     * @param key 键
     * @param map 字段-值映射
     */
    public void hPutAll(String key, Map<String, String> map) {
        stringRedisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 当哈希字段不存在时设置值
     *
     * @param key     键
     * @param hashKey 哈希键
     * @param value   值
     * @return 是否设置成功
     */
    public boolean hPutIfAbsent(String key, String hashKey, String value) {
        return Boolean.TRUE.equals(stringRedisTemplate.opsForHash().putIfAbsent(key, hashKey, value));
    }

    /**
     * 获取哈希字段值
     *
     * @param key     键
     * @param hashKey 哈希键
     * @return 值
     */
    public String hGet(String key, String hashKey) {
        return (String) stringRedisTemplate.opsForHash().get(key, hashKey);
    }

    /**
     * 获取所有哈希字段和值
     *
     * @param key 键
     * @return 字段-值映射
     */
    public Map<String, String> hGetAll(String key) {
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(key);
        Map<String, String> result = new HashMap<>();
        entries.forEach((k, v) -> result.put(k.toString(), v.toString()));
        return result;
    }

    /**
     * 获取多个哈希字段值
     *
     * @param key      键
     * @param hashKeys 哈希键集合
     * @return 值列表
     */
    public List<String> hMultiGet(String key, Collection<String> hashKeys) {
        List<Object> values = stringRedisTemplate.opsForHash().multiGet(key, Arrays.asList(hashKeys.toArray()));
        List<String> result = new ArrayList<>();
        for (Object value : values) {
            result.add(value != null ? value.toString() : null);
        }
        return result;
    }

    /**
     * 删除哈希字段
     *
     * @param key      键
     * @param hashKeys 哈希键数组
     * @return 删除数量
     */
    public long hDelete(String key, Object... hashKeys) {
        return stringRedisTemplate.opsForHash().delete(key, hashKeys);
    }

    /**
     * 判断哈希字段是否存在
     *
     * @param key     键
     * @param hashKey 哈希键
     * @return 是否存在
     */
    public boolean hHasKey(String key, String hashKey) {
        return stringRedisTemplate.opsForHash().hasKey(key, hashKey);
    }

    /**
     * 哈希字段自增
     *
     * @param key     键
     * @param hashKey 哈希键
     * @param delta   增量
     * @return 自增后的值
     */
    public long hIncrement(String key, String hashKey, long delta) {
        return stringRedisTemplate.opsForHash().increment(key, hashKey, delta);
    }

    /**
     * 获取所有哈希字段
     *
     * @param key 键
     * @return 字段集合
     */
    public Set<String> hKeys(String key) {
        Set<Object> keys = stringRedisTemplate.opsForHash().keys(key);
        Set<String> result = new HashSet<>();
        keys.forEach(k -> result.add(k.toString()));
        return result;
    }

    /**
     * 获取所有哈希值
     *
     * @param key 键
     * @return 值集合
     */
    public List<String> hValues(String key) {
        List<Object> values = stringRedisTemplate.opsForHash().values(key);
        List<String> result = new ArrayList<>();
        values.forEach(v -> result.add(v.toString()));
        return result;
    }

    /**
     * 获取哈希大小
     *
     * @param key 键
     * @return 字段数量
     */
    public long hSize(String key) {
        return stringRedisTemplate.opsForHash().size(key);
    }

    // ============================= 高级功能 =============================

    /**
     * 获取分布式锁
     *
     * @param lockKey    锁键
     * @param requestId  请求标识
     * @param expireTime 过期时间(毫秒)
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey, String requestId, long expireTime) {
        return setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS);
    }

    /**
     * 释放分布式锁
     *
     * @param lockKey   锁键
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) " +
                "else " +
                "return 0 " +
                "end";

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);
        return result != null && result == 1;
    }

    /**
     * 获取并设置新值（带过期时间）
     *
     * @param key     键
     * @param value   新值
     * @param timeout 过期时间
     * @param unit    时间单位
     * @return 旧值
     */
    public String getAndSetWithExpire(String key, String value, long timeout, TimeUnit unit) {
        String oldValue = getAndSet(key, value);
        if (oldValue != null) {
            expire(key, timeout, unit);
        }
        return oldValue;
    }

    /**
     * 获取并延长过期时间
     *
     * @param key     键
     * @param timeout 延长时间
     * @param unit    时间单位
     * @return 值
     */
    public String getAndExpire(String key, long timeout, TimeUnit unit) {
        String value = get(key);
        if (value != null) {
            expire(key, timeout, unit);
        }
        return value;
    }

    /**
     * 批量设置值
     *
     * @param keyValueMap 键值映射
     */
    public void multiSet(Map<String, String> keyValueMap) {
        stringRedisTemplate.opsForValue().multiSet(keyValueMap);
    }

    /**
     * 批量获取值
     *
     * @param keys 键集合
     * @return 值列表
     */
    public List<String> multiGet(Collection<String> keys) {
        return stringRedisTemplate.opsForValue().multiGet(keys);
    }

    // ============================= 测试方法 =============================
    public void testOperations() {
        // String 类型测试
        String stringKey = "test:string:key";
        set(stringKey, "Hello Redis");
        System.out.println("Get value: " + get(stringKey));

        // 带过期时间设置
        set(stringKey + ":temp", "Temporary value", 30, TimeUnit.SECONDS);

        // 自增操作
        String counterKey = "test:counter";
        System.out.println("Increment: " + increment(counterKey));
        System.out.println("Increment by 5: " + increment(counterKey, 5));

        // Hash 类型测试
        String hashKey = "test:hash:user";
        hPut(hashKey, "name", "John");
        hPut(hashKey, "age", "30");

        Map<String, String> userData = new HashMap<>();
        userData.put("email", "john@example.com");
        userData.put("city", "New York");
        hPutAll(hashKey, userData);

        System.out.println("User email: " + hGet(hashKey, "email"));
        System.out.println("All fields: " + hGetAll(hashKey));

        // 分布式锁测试
        String lockKey = "resource:lock";
        String requestId = UUID.randomUUID().toString();
        if (tryLock(lockKey, requestId, 5000)) {
            try {
                System.out.println("Lock acquired, doing work...");
                // 模拟业务操作
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                if (releaseLock(lockKey, requestId)) {
                    System.out.println("Lock released");
                }
            }
        }
    }
}